package com.taotao.wechat.util;

import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.net.URLEncoder;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

//import org.apache.commons.lang3.RandomStringUtils;


/**
 * 调用Rest接口时，对参数进行加密、解密处理
 * TODO: 暂时用不上，注释掉因为缺少包RandomStringUtils引起的错误
 */
public class RestCryptoUtil {
    private static final int KEY_LENGTH = 16;
    private static final String API_ID = "267BCFE915004BCA";
    private static final String API_SECRET = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMVSGUdk3cfqis5IQhrJxl7KigDC76NMmfNS4a1yi/2FAY4juFrEigthv7uXISLSVtLJULQf+F4p4YiTlkC4aQMCAwEAAQ==";

    private static final String KEY_APP_ID = "app_id";
    private static final String KEY_REQ_MSG = "req_msg";
    private static final String KEY_REQ_KEY = "req_key";
    private static final String HTTP_RELATIVE_URI_FORMAT = "%s?" + KEY_APP_ID + "=%s&"
            + KEY_REQ_MSG + "=%s&" + KEY_REQ_KEY + "=%s&sign=%s";
    private static final String KEY_REQUEST_PARAM_SEPARATOR = "&";

    public static void main(String[] args) {
        System.out.println("==> " + generalEncryptUrl("GET", "/level1/v1/trans", "hello"));
    }

    /**
     * 生成http请求的加密后的相对url
     *
     * @param httpType    http请求方式 GET or POST
     * @param relativeUri 请求的URI路径（不含HOST）
     * @param src         明文
     * @return 加密后的url
     */
    public static String generalEncryptUrl(String httpType, String relativeUri, String src) {
        /**随机生成加密交易报文明文的秘钥*/
        String key = ""; // RandomStringUtils.randomAlphanumeric(KEY_LENGTH);
        System.out.println("key: " + key);
        String req_msg = messageEncrypt(src, key);
        System.out.println("req_msg: " + req_msg);
        String req_key = keyEncrypt(key);
        System.out.println("req_key: " + req_key);

        //测试
//        String req_key = "3DFCEC393F8CD0782050B929458B552BE954B98B9CDF0ABF0D4883DE05322FA294C6670C91CFA231F17256A66D7E664C76D5D7C2BAF87AD9752FC64F67DED331";
//        String req_msg = "qGnuEJQQKAJ5E4Aie/IVO+kwaw2LBjae03wLLNdJrTI=";

        Map<String, String> params = new HashMap<String, String>();
        params.put(KEY_APP_ID, API_ID);
        params.put(KEY_REQ_KEY, req_key);
        params.put(KEY_REQ_MSG, req_msg);

        String sign = sign(httpType, relativeUri, params);
        System.out.println("sign: " + sign);

        return String.format(HTTP_RELATIVE_URI_FORMAT, relativeUri, API_ID, req_msg, req_key, sign);
    }

    /**
     * @param src 交易报文明文
     * @return 交易报文密文
     */
    private static String messageEncrypt(String src, String key) {
        try {
            return AESUtil.encrypt(src, key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private static String keyEncrypt(String key) {
        try {
            return RSAUtil.encryptByPrivateKey(key, API_SECRET);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 生成签名
     *
     * @param httpType    http请求方式 GET or POST
     * @param relativeUri 请求的URI路径（不含HOST）
     * @param params      请求参数map
     * @return 签名
     */
    private static String sign(String httpType, String relativeUri, Map<String, String> params) {
        try {
            String encoderUri = URLEncoder.encode(relativeUri, "utf-8");

            Set<String> keySet = params.keySet();
            String[] keyArr = keySet.toArray(new String[keySet.size()]);
            Arrays.sort(keyArr);
            StringBuilder sb = new StringBuilder();
            for (String key : keyArr) {
                sb.append(key).append("=").append(params.get(key))
                        .append(KEY_REQUEST_PARAM_SEPARATOR);
            }
            String encoderParam = URLEncoder.encode(sb.toString().substring(0, sb.length() - 1), "utf-8");

            String result = httpType + KEY_REQUEST_PARAM_SEPARATOR + encoderUri + KEY_REQUEST_PARAM_SEPARATOR + encoderParam;
            String key = API_SECRET + KEY_REQUEST_PARAM_SEPARATOR;
            return HMACSHA1.encrypt(result, key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * AES-128加密算法：密钥长度128bit(16字符)，工作模式ECB，填充方式PKCS5Padding
     */
    private static class AESUtil {
        /**
         * 加密
         *
         * @param src 需要加密的字符串
         * @param key key
         * @return 加密后的字符串
         * @throws Exception
         */
        private static String encrypt(String src, String key) throws Exception {
            if (key == null) {
                System.out.print("Key为空null");
                return null;
            }
            // 判断Key是否为16位
            if (key.length() != 16) {
                System.out.print("Key长度不是16位");
                return null;
            }
            byte[] raw = key.getBytes("utf-8");
            SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");//算法/模式/补码方式
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
            byte[] encrypted = cipher.doFinal(src.getBytes("utf-8"));
            return new String(encrypted, "utf-8");
        }

        /**
         * 解密
         *
         * @param src 需要解密的字符串
         * @param key key
         * @return 解密后的字符串
         * @throws Exception
         */
        private static String decrypt(String src, String key) throws Exception {
            if (key == null) {
                System.out.print("Key为空null");
                return null;
            }
            // 判断Key是否为16位
            if (key.length() != 16) {
                System.out.print("Key长度不是16位");
                return null;
            }
            byte[] raw = key.getBytes("utf-8");
            SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec);
            byte[] original = cipher.doFinal(src.getBytes("utf-8"));
            return new String(original, "utf-8");
        }
    }

    private static class RSAUtil {
        /**
         * 加密算法RSA
         */
        private static final String KEY_ALGORITHM = "RSA";
        /**
         * 获取公钥的key
         */
        private static final String PUBLIC_KEY = "RSAPublicKey";
        /**
         * 获取私钥的key
         */
        private static final String PRIVATE_KEY = "RSAPrivateKey";
        /**
         * RSA最大加密明文大小
         */
        private static final int MAX_ENCRYPT_BLOCK = 117;
        /**
         * RSA最大解密密文大小
         */
        private static final int MAX_DECRYPT_BLOCK = 128;

        /**
         * 生成密钥对(公钥和私钥)
         *
         * @return 包含密钥对的Map
         * @throws Exception
         */
        private static Map<String, Object> generalKeyPair() throws Exception {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
            keyPairGenerator.initialize(1024);
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
            RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
            Map<String, Object> keyMap = new HashMap<String, Object>(2);
            keyMap.put(PUBLIC_KEY, publicKey);
            keyMap.put(PRIVATE_KEY, privateKey);
            return keyMap;
        }

        /**
         * 私钥加密
         *
         * @param src        源数据
         * @param privateKey 私钥
         * @return 加密后的数据
         */
        private static String encryptByPrivateKey(String src, String privateKey) throws Exception {
            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(privateKey.getBytes("utf-8"));
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.ENCRYPT_MODE, privateK);

            byte[] srcData = src.getBytes("utf-8");
            int len = srcData.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            //分段加密
            while (len - offSet > 0) {
                if (len - offSet > MAX_ENCRYPT_BLOCK) {
                    cache = cipher.doFinal(srcData, offSet, MAX_ENCRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(srcData, offSet, len - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_ENCRYPT_BLOCK;
            }
            byte[] encryptedData = out.toByteArray();
            out.close();
            return new String(encryptedData, "utf-8");
        }

        /**
         * 公钥加密
         *
         * @param src       源数据
         * @param publicKey 公钥
         * @return 加密后的数据
         */
        private static String encryptByPublicKey(String src, String publicKey) throws Exception {
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKey.getBytes("utf-8"));
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            Key publicK = keyFactory.generatePublic(x509EncodedKeySpec);
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.ENCRYPT_MODE, publicK);

            byte[] srcData = src.getBytes("utf-8");
            int len = srcData.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            //分段加密
            while (len - offSet > 0) {
                if (len - offSet > MAX_ENCRYPT_BLOCK) {
                    cache = cipher.doFinal(srcData, offSet, MAX_ENCRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(srcData, offSet, len - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_ENCRYPT_BLOCK;
            }
            byte[] encryptedData = out.toByteArray();
            out.close();
            return new String(encryptedData, "utf-8");
        }

        /**
         * 私钥解密
         *
         * @param encryptedStr 已加密数据
         * @param privateKey   私钥
         * @return 解密后的数据
         * @throws Exception
         */
        private static String decryptByPrivateKey(String encryptedStr, String privateKey) throws Exception {
            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(privateKey.getBytes("utf-8"));
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.DECRYPT_MODE, privateK);

            byte[] encryptedData = encryptedStr.getBytes("utf-8");
            int len = encryptedData.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            //分段加密
            while (len - offSet > 0) {
                if (len - offSet > MAX_DECRYPT_BLOCK) {
                    cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(encryptedData, offSet, len - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_DECRYPT_BLOCK;
            }
            byte[] srcData = out.toByteArray();
            out.close();
            return new String(srcData, "utf-8");
        }

        /**
         * 公钥解密
         *
         * @param encryptedStr 已加密数据
         * @param publickey    公钥
         * @return 解密后的数据
         * @throws Exception
         */
        private static String decryptByPublicKey(String encryptedStr, String publickey) throws Exception {
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publickey.getBytes("utf-8"));
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            Key publicK = keyFactory.generatePublic(x509EncodedKeySpec);
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.DECRYPT_MODE, publicK);

            byte[] encryptedData = encryptedStr.getBytes("utf-8");
            int len = encryptedData.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            //分段加密
            while (len - offSet > 0) {
                if (len - offSet > MAX_DECRYPT_BLOCK) {
                    cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(encryptedData, offSet, len - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_DECRYPT_BLOCK;
            }
            byte[] srcData = out.toByteArray();
            out.close();
            return new String(srcData, "utf-8");
        }
    }

    private static class HMACSHA1 {
        private static final String HMAC_SHA1 = "HmacSHA1";

        /**
         * 加密
         *
         * @param src 需要加密的字符串
         * @param key key
         * @return 加密后的字符串，经过Base64编码
         * @throws Exception
         */
        private static String encrypt(String src, String key) throws Exception {
            SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes("utf-8"), HMAC_SHA1);
            Mac mac = Mac.getInstance(HMAC_SHA1);
            mac.init(secretKeySpec);
            byte[] rawHmac = mac.doFinal(src.getBytes("utf-8"));
            return new Base64().encodeToString(rawHmac);
        }
    }
}
